package com.sp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sp.exception.BusinessException;
import com.sp.exception.TokenException;
import com.sp.mapper.EmployeeAndEmployeeRoleMapper;
import com.sp.mapper.EmployeeMapper;
import com.sp.model.dto.EmployeeDTO;
import com.sp.model.entity.Employee;
import com.sp.model.entity.EmployeeDetails;
import com.sp.model.entity.EmployeeRole;
import com.sp.properties.JWTProperties;
import com.sp.service.EmployeeRoleService;
import com.sp.service.EmployeeService;
import com.sp.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.sp.constant.EmployeeMessageConstant.*;
import static com.sp.constant.RedisConstant.*;

/**
 * 管理员服务实现类
 *
 * @author tong
 */
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {

    @Autowired
    private JWTProperties jwtProperties;

    @Autowired
    private EmployeeRoleService employeeRoleService;

    @Autowired
    private EmployeeAndEmployeeRoleMapper employeeAndEmployeeRoleMapper;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private AuthenticationConfiguration authenticationConfiguration;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public Employee login(String username, String password, String checkCode, String checkCodeId) {
        if (username == null || "".equals(username) || password == null || "".equals(password) ||
                checkCode == null || "".equals(checkCode) || checkCodeId == null || "".equals(checkCodeId)) {
            throw new BusinessException(EMPLOYEE_LOGIN_FAIL + "：" + PARAM_ERROR);
        }

        // 根据key去redis中获取验证码
        String code = stringRedisTemplate.opsForValue().get(LOGIN_CHECKCODE_KEY + checkCodeId);
        // 取完就删除
        stringRedisTemplate.delete(LOGIN_CHECKCODE_KEY + checkCodeId);
        if (code == null || code.length() == 0) {
            throw new BusinessException(CHECKCODE_TIMEOUT);
        }
        // 校验验证码
        if (!code.equalsIgnoreCase(checkCode)) {
            throw new BusinessException(CHECKCODE_ERROR);
        }

        // 使用AuthenticationManager进行认证
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, password);
        Authentication authenticate;
        try {
            authenticate = authenticationConfiguration.getAuthenticationManager().authenticate(usernamePasswordAuthenticationToken);
        } catch (Exception e) {
            throw new BusinessException(PASSWORD_ERROR);
        }

        // 认证成功，从中取出Details对象
        EmployeeDetails employeeDetails = (EmployeeDetails) authenticate.getPrincipal();
        // UserDetails对象存入redis，用于jwt认证
        long timeout = jwtProperties.getTimeout();
        stringRedisTemplate.opsForValue().set(EMPLOYEE_LOGIN_USERNAME + username, JSONObject.toJSONString(employeeDetails),
                timeout * 2, TimeUnit.MILLISECONDS);

        // 从Details中取出Employee对象
        Employee employee = employeeDetails.getEmployee();
        // 登录成功，设置双token
        HashMap<String, Object> map = new HashMap<>(2);
        map.put("id", employee.getId());
        map.put("name", employee.getName());
        map.put("username", employee.getUsername());
        employee.setToken(JwtUtil.createJWT(jwtProperties.getSecretKey(), timeout, map));
        employee.setRefreshToken(JwtUtil.createJWT(jwtProperties.getSecretKey(), timeout * 2, map));
        return employee;
    }

    @Override
    public boolean logout() {
        // logout请求，要授权才能访问
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        EmployeeDetails EmployeeDetails = (EmployeeDetails) authentication.getPrincipal();
        String username = EmployeeDetails.getEmployee().getUsername();

        return Boolean.TRUE.equals(stringRedisTemplate.delete(EMPLOYEE_LOGIN_USERNAME + username));
    }

    @Override
    public Map<String, Object> generateCheckCode(int charLen, int lineNum) {
        int width = 100, height = 50;

        //创建验证码图片对象
        BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

        //美化图片
        //获取画笔对象
        Graphics graphics = bufferedImage.getGraphics();
        //设置画笔颜色
        graphics.setColor(Color.GRAY);
        //填充背景色
        graphics.fillRect(0, 0, width, height);

        //画边框
        graphics.setColor(Color.black);
        graphics.drawRect(0, 0, width - 1, height - 1);

        //写验证码，随机序列
        String str = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
        Random random = new Random();
        //String 字体，int 风格，int 字号
        Font font = new Font("黑体", Font.BOLD, 30);
        graphics.setFont(font);
        graphics.setColor(Color.YELLOW);
        StringBuilder stringBuilder = new StringBuilder();
        int strWidth = (width - 20) / 4;
        for (int i = 0; i < charLen; i++) {
            //闭区间
            int index = random.nextInt(str.length());
            char ch = str.charAt(index);
            stringBuilder.append(ch);
            //坐标点是左下角，坐标和字体大小单位不同
            graphics.drawString(ch + "", 10 + strWidth * i, height * 3 / 4);
        }

        //将验证码存入redis
        String id = UUID.randomUUID().toString();
        stringRedisTemplate.opsForValue().set(LOGIN_CHECKCODE_KEY + id, stringBuilder.toString(),
                LOGIN_CHECKCODE_TTL, TimeUnit.MINUTES);

        //干扰线
        graphics.setColor(Color.BLACK);
        for (int i = 0; i < lineNum; i++) {
            int x1 = random.nextInt(width);
            int x2 = random.nextInt(width);
            int y1 = random.nextInt(height);
            int y2 = random.nextInt(height);
            graphics.drawLine(x1, y1, x2, y2);
        }

        //输出图像到页面展示
        ByteArrayOutputStream byteArrayOutputStream = null;
        try {
            byteArrayOutputStream = new ByteArrayOutputStream();
            ImageIO.write(bufferedImage, "jpg", byteArrayOutputStream);
            byte[] bytes = byteArrayOutputStream.toByteArray();

            Map<String, Object> map = new HashMap<>(2);
            map.put("checkCodeId", id);
            map.put("checkCode", bytes);
            return map;
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void removeCheckCode(String checkCodeId) {
        stringRedisTemplate.delete(LOGIN_CHECKCODE_KEY + checkCodeId);
    }

    @Override
    public Map<String, String> refreshToken(String username, String refreshToken) {
        if (StrUtil.isBlank(username) || StrUtil.isBlank(refreshToken)) {
            throw new TokenException(EMPLOYEE_NOT_LOGIN);
        }

        Claims claims;
        try {
            claims = JwtUtil.parseJWT(jwtProperties.getSecretKey(), refreshToken);
        } catch (Exception e) {
            //不通过，响应401状态码，生成的token已经过期
            throw new TokenException(TOKEN_ERROR);
        }

        if (Objects.equals(username, claims.get("username"))) {
            // 刷新token未过期，续期
            long timeout = jwtProperties.getTimeout();
            String token = JwtUtil.createJWT(jwtProperties.getSecretKey(), timeout, claims);
            refreshToken = JwtUtil.createJWT(jwtProperties.getSecretKey(), timeout * 2, claims);
            HashMap<String, String> map = new HashMap<>();
            map.put("token", token);
            map.put("refreshToken", refreshToken);

            // 刷新redis中缓存
            stringRedisTemplate.expire(EMPLOYEE_LOGIN_USERNAME + username, timeout * 2, TimeUnit.MILLISECONDS);

            return map;
        } else {
            // 用户名不相等
            throw new TokenException(TOKEN_EMPLOYEE_ERROR);
        }
    }

    @Override
    public Employee selectById(Integer id) {
        if (id == null) {
            throw new BusinessException(SELECT_EMPLOYEE_FAIL + "：" + PARAM_ERROR);
        }

        return baseMapper.getById(id);
    }

    @Override
    public Employee selectByUsername(String username) {
        if (StrUtil.isBlank(username)) {
            throw new BusinessException(SELECT_EMPLOYEE_FAIL + "：" + PARAM_ERROR);
        }

        String json = stringRedisTemplate.opsForValue().get(EMPLOYEE_INFO_USERNAME + username);
        if (json == null) {
            // 缓存不存在
            Employee employee = baseMapper.selectByUsername(username);
            if (employee == null) {
                stringRedisTemplate.opsForValue().set(EMPLOYEE_INFO_USERNAME + username, "", EMPLOYEE_INFO_NULL_TTL, TimeUnit.MINUTES);
            } else {
                stringRedisTemplate.opsForValue().set(EMPLOYEE_INFO_USERNAME + username, JSONObject.toJSONString(employee), EMPLOYEE_INFO_TTL, TimeUnit.MINUTES);
            }
            return employee;
        }

        if (StrUtil.isNotBlank(json)) {
            return JSONObject.parseObject(json, Employee.class);
        }
        return null;
    }

    @Transactional
    @Override
    public boolean addEmployee(EmployeeDTO employeeDTO) {
        if (employeeDTO == null ||
                employeeDTO.getRoleIds() == null || employeeDTO.getRoleIds().isEmpty() ||
                StrUtil.isBlank(employeeDTO.getPhone())) {
            throw new BusinessException(ADD_EMPLOYEE_FAIL + "：" + PARAM_ERROR);
        }

        // 判断权限角色是否存在
        this.inspectEmployeeDTORole(employeeDTO, ADD_EMPLOYEE_FAIL);

        Employee employee = BeanUtil.copyProperties(employeeDTO, Employee.class);

        // 随机生成username，我觉得最好别走缓存
        while (true) {
            String username = generateUserName(10);
            Employee selectByUsername = baseMapper.selectByUsername(username);
            if (selectByUsername == null) {
                employee.setUsername(username);
                break;
            }
        }

        //密码加密
        String password = employee.getPassword();
        if (StrUtil.isBlank(password)) {
            password = DEFAULT_PASSWORD;
        }
        employee.setPassword(passwordEncoder.encode(password));

        // 保存管理员信息
        this.save(employee);

        // 保存管理员与权限角色关联
        List<Integer> roleIds = employeeDTO.getRoleIds();
        if (employeeAndEmployeeRoleMapper.add(employee.getId(), roleIds) != roleIds.size()) {
            throw new RuntimeException("添加数量不正确");
        }
        return true;
    }

    /**
     * 随机生成username
     *
     * @param length
     * @return
     */
    private String generateUserName(int length) {
        StringBuilder stringBuilder = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < length; i++) {
            stringBuilder.append(random.nextInt(10));
        }
        return stringBuilder.toString();
    }

    @Transactional
    @Override
    public boolean deleteEmployees(List<Integer> ids) {
        // 删除用户缓存
        this.deleteEmployeeFromRedis(ids);

        // 删除管理员与权限角色关联
        employeeAndEmployeeRoleMapper.deleteByEmployeeIds(ids);
        return this.removeByIds(ids);
    }

    @Transactional
    @Override
    public boolean updateEmployee(EmployeeDTO employeeDTO) {
        if (employeeDTO == null || employeeDTO.getId() == null ||
                employeeDTO.getRoleIds() == null || employeeDTO.getRoleIds().isEmpty()) {
            throw new BusinessException(UPDATE_EMPLOYEE_FAIL + "：" + PARAM_ERROR);
        }

        // 判断权限角色是否存在
        this.inspectEmployeeDTORole(employeeDTO, UPDATE_EMPLOYEE_FAIL);

        // 删除该用户缓存
        this.deleteEmployeeFromRedis(employeeDTO.getId());

        String password = employeeDTO.getPassword();
        if (StrUtil.isNotBlank(password)) {
            employeeDTO.setPassword(passwordEncoder.encode(password));
        } else {
            employeeDTO.setPassword(null);
        }

        Employee employee = BeanUtil.copyProperties(employeeDTO, Employee.class);

        // 删除管理员与权限角色关联
        Integer employeeId = employee.getId();
        employeeAndEmployeeRoleMapper.deleteByEmployeeId(employeeId);

        List<Integer> roleIds = employeeDTO.getRoleIds();
        if (employeeAndEmployeeRoleMapper.add(employeeId, roleIds) != roleIds.size()) {
            throw new BusinessException(UPDATE_EMPLOYEE_FAIL + "添加权限角色数量不正确");
        }

        return this.updateById(employee);
    }

    /**
     * 校验employeeDTO中的权限角色id是否存在
     *
     * @param employeeDTO
     * @param message
     */
    private void inspectEmployeeDTORole(EmployeeDTO employeeDTO, String message) {
        List<Integer> roleIds = employeeDTO.getRoleIds();
        Map<Integer, EmployeeRole> employeeRolesMap = employeeRoleService.selectAllMap();
        if (roleIds.stream().anyMatch(roleId -> employeeRolesMap.get(roleId) == null)) {
            throw new BusinessException(message + "：权限角色不存在");
        }
    }

    @Override
    public IPage<Employee> page(Integer currentPage, Integer pageSize, EmployeeDTO employeeDTO) {
        if (currentPage == null) {
            currentPage = 1;
        }

        if (pageSize == null) {
            pageSize = 10;
        }

        //条件查询
        QueryWrapper<Employee> queryWrapper = new QueryWrapper<>();
        boolean flag = false;
        String role = null;
        if (employeeDTO != null) {
            // 模糊查询，左右%
            queryWrapper.like(StrUtil.isNotBlank(employeeDTO.getUsername()), "`e1`.`username`", employeeDTO.getUsername())
                    .like(StrUtil.isNotBlank(employeeDTO.getName()), "`e1`.`name`", employeeDTO.getName())
                    .eq(StrUtil.isNotBlank(employeeDTO.getSex()), "`e1`.`sex`", employeeDTO.getSex())
                    .like(StrUtil.isNotBlank(employeeDTO.getPhone()), "`e1`.`phone`", employeeDTO.getPhone())
                    .gt(null != employeeDTO.getStartTime(), "`e1`.`create_time`", employeeDTO.getStartTime())
                    .lt(null != employeeDTO.getEndTime(), "`e1`.`create_time`", employeeDTO.getEndTime());

            // 判断查询条件是否有权限角色
            role = employeeDTO.getRole();
            if (StrUtil.isNotBlank(role)) {
                flag = true;
            }
        }
        // 3.4.3以后变为Page.of()
        IPage<Employee> page = new Page<>(currentPage, pageSize);
        if (flag) {
            return baseMapper.pageWithRole(page, queryWrapper, role);
        }
        return baseMapper.page(page, queryWrapper);
    }

    @Transactional
    @Override
    public boolean updateMe(EmployeeDTO employeeDTO) {
        if (employeeDTO == null || employeeDTO.getId() == null) {
            throw new BusinessException(UPDATE_EMPLOYEE_FAIL + "：" + PARAM_ERROR);
        }

        // 删除该用户缓存
        this.deleteEmployeeFromRedis(employeeDTO.getId());

        return this.updateById(BeanUtil.copyProperties(employeeDTO, Employee.class));
    }

    @Transactional
    @Override
    public boolean updatePassword(EmployeeDTO employeeDTO) {
        if (employeeDTO == null || employeeDTO.getId() == null) {
            throw new BusinessException(UPDATE_EMPLOYEE_FAIL + "：" + PARAM_ERROR);
        }

        Integer id = employeeDTO.getId();

        // 比较原密码是否正确
        String oldPassword = employeeDTO.getPassword();
        Employee e = this.selectById(id);
        if (!passwordEncoder.matches(oldPassword, e.getPassword())) {
            throw new BusinessException(UPDATE_EMPLOYEE_FAIL + "：原密码错误");
        }

        // 删除该用户缓存
        this.deleteEmployeeFromRedis(employeeDTO.getId());

        Employee employee = new Employee();
        employee.setId(employeeDTO.getId());
        String newPassword = employeeDTO.getNewPassword();
        if (StrUtil.isNotBlank(newPassword)) {
            employee.setPassword(passwordEncoder.encode(newPassword));
        } else {
            employee.setPassword(passwordEncoder.encode(DEFAULT_PASSWORD));
        }

        if (this.updateById(employee)) {
            // 更新成功，退出登录，删除redis中登录信息的缓存
            stringRedisTemplate.delete(EMPLOYEE_LOGIN_USERNAME + e.getUsername());
        }
        return true;
    }

    /**
     * 删除管理员信息缓存
     *
     * @param id
     */
    private void deleteEmployeeFromRedis(Integer id) {
        Employee employee = baseMapper.selectById(id);
        stringRedisTemplate.delete(EMPLOYEE_LOGIN_USERNAME + employee.getUsername());
    }

    private void deleteEmployeeFromRedis(List<Integer> ids) {
        List<Employee> employees = baseMapper.selectBatchIds(ids);
        List<String> keys = employees.stream().map(employee -> EMPLOYEE_LOGIN_USERNAME + employee.getUsername())
                .collect(Collectors.toList());
        stringRedisTemplate.delete(keys);
    }

}
